﻿using System;
using System.Reflection;
using System.Reflection.Emit;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interception.Infrastructure;
using MugenInjection.Interception.Interface;

namespace MugenInjection.Interception.Proxy
{
    /// <summary>
    /// A base implementation of the <see cref="IProxyFactory"/>.
    /// </summary>
    public class MugenInjectionProxyFactory : IProxyFactory
    {
        #region Fields

        private static readonly object Locker = new object();
        private static AssemblyBuilder _assemblyBuilder;
        private static ModuleBuilder _dynamicModule;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="MugenInjectionProxyFactory"/> class.
        /// </summary>
        public MugenInjectionProxyFactory()
            : this(new DefaultProxyBuilder())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MugenInjectionProxyFactory"/> class.
        /// </summary>
        public MugenInjectionProxyFactory(IProxyBuilder proxyBuilder)
        {
            ProxyBuilder = proxyBuilder;
            lock (Locker)
            {
                if (_assemblyBuilder != null && _dynamicModule != null)
                {
                    proxyBuilder.DynamicModule = _dynamicModule;
                    return;
                }
                var assemblyName = new AssemblyName(ProxyUtils.DefultAssemblyNameInternal)
                    {
                        Version = new Version(0, 0, 0),
                    };

                assemblyName.SetPublicKey(Convert.FromBase64String("ACQAAASAAACUAAAABgIAAAAkAABSU0ExAAQAAAEAAQBVFfMsldescgINEX+golsNwr5kg4J//mJIdbg4yeAsQzReVm1RWUN/Ht35rp8WB18alxOUwdCXW3mlU7rVworx2/Hjb1tbAUKRizOpCIThYh74v8KiQzq0BuFZzDrS7LeSaG76rmpg989pAWypjuKw6/jcyF+fDA4h0ioYJ7gwqg=="));
                assemblyName.SetPublicKeyToken(Convert.FromBase64String("/katQh3TsOY="));

#if NETFX_CORE
                _assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
                _dynamicModule = _assemblyBuilder.DefineDynamicModule(ProxyUtils.DefultAssemblyNameInternal);
#else
                _assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
                _dynamicModule = _assemblyBuilder.DefineDynamicModule(ProxyUtils.DefultAssemblyNameInternal, true);
#endif


                /* _assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
                 _dynamicModule = _assemblyBuilder.DefineDynamicModule(ProxyUtils.DefultAssemblyNameInternal, "test.dll", true);*/

                proxyBuilder.DynamicModule = _dynamicModule;
            }
        }

        #endregion

        #region Implementation of IProxyFactory

        /// <summary>
        /// Gets the <see cref="IProxyBuilder"/>.
        /// </summary>
        public IProxyBuilder ProxyBuilder { get; private set; }


        /// <summary>
        /// Creates a proxy based on the underlying type.
        /// </summary>
        /// <param name="baseType">The specified base type for proxy.</param>
        /// <param name="proxyType">The result proxy <see cref="Type"/>.</param>
        /// <returns>The specified <see cref="IProxyCollection"/> for intercept.</returns>
        public Func<IProxyCollection> CreateProxy(Type baseType, out Type proxyType)
        {
            Validate.ArgumentNotNull(baseType, "baseType");
#if NETFX_CORE
            if (!baseType.GetTypeInfo().IsClass)
#else
            if (!baseType.IsClass)
#endif
                throw new ArgumentException("The specified types is not an class", "baseType");
            ProxyUtils.CheckNotGenericTypeDefinition(baseType, "baseType");
            IProxyCollection proxyCollection;
            lock (Locker)
            {
                proxyType = ProxyBuilder.CreateProxy(baseType, new Type[0], out proxyCollection);
            }
            return proxyCollection.Clone;
        }


        /// <summary>
        /// Creates a proxy based on the underlying type and interfaces.
        /// </summary>
        /// <param name="baseType">The base type for proxy.</param>
        /// <param name="interfaces">The specified interfaces for proxy.</param>
        /// <param name="proxyType">The result proxy <see cref="Type"/>.</param>
        /// <returns>The <see cref="IProxyCollection"/> for intercept.</returns>
        public Func<IProxyCollection> CreateProxy(Type baseType, Type[] interfaces, out Type proxyType)
        {
            Validate.ArgumentNotNull(baseType, "baseType");
            Validate.ArgumentNotNull(interfaces, "interfaces");
            if (!ProxyUtils.IsInterfacesAndNotDuplicated(interfaces))
                throw new ArgumentException("The specified types is not an interface", "interfaces");
#if NETFX_CORE
            if (!baseType.GetTypeInfo().IsClass)
#else
            if (!baseType.IsClass)
#endif
                throw new ArgumentException("The specified types is not an class", "baseType");
            ProxyUtils.CheckNotGenericTypeDefinition(baseType, "service");
            ProxyUtils.CheckNotGenericTypeDefinition(interfaces, "interfaces");

            IProxyCollection proxyCollection;
            lock (Locker)
            {
                proxyType = ProxyBuilder.CreateProxy(baseType, interfaces, out proxyCollection);
            }
            return proxyCollection.Clone;
        }

        /// <summary>
        /// Creates a proxy based on interfaces.
        /// </summary>
        /// <param name="interfaces">The specified interfaces for proxy.</param>
        /// <param name="proxyType">The result proxy <see cref="Type"/>.</param>
        /// <returns><see cref="IProxyCollection"/> for intercept.</returns>
        public Func<IProxyCollection> CreateProxy(Type[] interfaces, out Type proxyType)
        {
            Validate.ArgumentNotNullAndNonZeroLength(interfaces, "interfaces");
            if (!ProxyUtils.IsInterfacesAndNotDuplicated(interfaces))
                throw new ArgumentException("The specified types is not an interface", "interfaces");
            ProxyUtils.CheckNotGenericTypeDefinition(interfaces, "interfaces");

            IProxyCollection proxyCollection;
            lock (Locker)
            {
                proxyType = ProxyBuilder.CreateProxy(typeof(object), interfaces, out proxyCollection);
            }
            return proxyCollection.Clone;
        }

        #endregion
    }
}